﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
using log4net;
using NHibernate.Linq;
using NHibernate.Transform;

// Test Webservice
// http://mvvm.granwehr.net/MVVMtutorialAdvanced-Web-DomainServiceBook.svc
// http://localhost:46620/MVVMtutorialAdvanced-Web-DomainServiceBook.svc

// Blogs
// Injecting Dependencies Into Hosted WCF Services Part II: http://burcakcakiroglu.com/?p=2148
// How to Intercept a WCF Message to track message size http://devlicio.us/blogs/derik_whittaker/archive/2011/02/03/how-to-intercept-a-wcf-message-to-track-message-size.aspx
// NHibernate on WCF http://www.igloocoder.com/archive/2008/07/21/nhibernate-on-wcf.aspx

namespace MVVMtutorialAdvanced.Web
{
	[EnableClientAccess]
	//[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Single)]
	[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
	[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] // Must for the DomainServiceFactory, because aspNetCompatibilityEnabled="true"
	public class DomainServiceBook : DomainService
	{
		private static ILog _log;

		//IBookRepository _context;
		private readonly NHibernate.ISessionFactory _sessionFactory;

		public DomainServiceBook(NHibernate.ISessionFactory sessionFactory)
		{
			try
			{
				_log = LogManager.GetLogger(typeof(DomainServiceBook));

				_log.Debug("---------------- DomainServiceBook");

				_sessionFactory = sessionFactory;
				//_context = new BookRepository();
			}
			catch (Exception ex)
			{
				_log.Fatal("Error in DomainServiceBook.Constructor.", ex);
				throw;
			}
		}

		// Overridden to support paging over POCO entities.
		protected override int Count<T>(IQueryable<T> query)
		{
			return query.Count();
		}


		#region country

		public IQueryable<CountryDTO> GetCountries()
		{
			try
			{
				_log.Debug("---------------- GetCountries");

				using (var session = _sessionFactory.OpenSession())
				using (var transaction = session.BeginTransaction())
				{
					CountryDTO countryDTO = null;
					var countries =
						session.QueryOver<Country>()
							.SelectList
							(
								l => l
									.Select(b => b.Id).WithAlias(() => countryDTO.Id)
									.Select(b => b.Alias).WithAlias(() => countryDTO.Alias)
							)
							.TransformUsing(Transformers.AliasToBean<CountryDTO>())
							.List<CountryDTO>();

					transaction.Commit();

					return countries.AsQueryable();
				}
			}
			catch (Exception ex)
			{
				_log.Fatal("Error in GetCountries.", ex);
				return null;
			}
		}

		#endregion country

		#region author

		//IEnumerable=Readonly, Q=Query
		public IEnumerable<AuthorQDTO> GetAuthorsQ()
		{
			try
			{
				using (var session = _sessionFactory.OpenSession())
				using (var transaction = session.BeginTransaction())
				{
					AuthorQDTO authorQdto = null;
					var authorQdtos =
						session.QueryOver<Author>()
							.SelectList
							(
								l => l
									.Select(b => b.Id).WithAlias(() => authorQdto.Id)
									.Select(b => b.FirstName).WithAlias(() => authorQdto.FirstName)
									.Select(b => b.LastName).WithAlias(() => authorQdto.LastName)
							)
							.TransformUsing(Transformers.AliasToBean<AuthorQDTO>())
							.List<AuthorQDTO>();

					transaction.Commit();

					return authorQdtos.AsQueryable();
				}
			}
			catch (Exception ex)
			{
				_log.Fatal("Error in GetAuthorsQ.", ex);
				return null;
			}
		}

		public IQueryable<Author> GetAuthors()
		{
			try
			{
				using (var session = _sessionFactory.OpenSession())
				using (var transaction = session.BeginTransaction())
				{
					var authors = session.QueryOver<Author>().List();
					transaction.Commit();

					return authors.AsQueryable();
				}
			}
			catch (Exception ex)
			{
				_log.Fatal("Error in GetAuthors.", ex);
				return null;
			}
		}

		public void InsertAuthor(Author author)
		{
			try
			{
				_log.DebugFormat("InsertAuthor. author.Id='{0}'", author.Id);

				using (var session = _sessionFactory.OpenSession())
				using (var transaction = session.BeginTransaction())
				{
					session.Save(author);
					transaction.Commit();
				}
			}
			catch (Exception ex)
			{
				_log.Fatal(
					author == null
						? "Error in InsertAuthor. author is null"
						: string.Format("Error in InsertAuthor. author.Id='{0}'", author.Id), ex);

				throw;
			}
		}

		public void DeleteAuthor(Author author)
		{
			try
			{
				_log.DebugFormat("DeleteAuthor. author.Id='{0}'", author.Id);

				using (var session = _sessionFactory.OpenSession())
				using (var transaction = session.BeginTransaction())
				{
					var bookAuthors = session.QueryOver<BookAuthor>()
						.Where(x => x.Author.Id == author.Id)
						.List();

					foreach (var bookAuthor in bookAuthors)
						session.Delete(bookAuthor);
					
					session.Delete(author);
					transaction.Commit();

					System.Threading.Thread.Sleep(1000);  //Show IsBusy for demo
				}
			}
			catch (Exception ex)
			{
				_log.Fatal(
					author == null
						? "Error in DeleteAuthor. author is null"
						: string.Format("Error in DeleteAuthor. author.Id='{0}'", author.Id), ex);
				throw;
			}
		}

		public void UpdateAuthor(Author author)
		{
			try
			{
				_log.DebugFormat("UpdateAuthor. author.Id='{0}'", author.Id);

				using (var session = _sessionFactory.OpenSession())
				using (var transaction = session.BeginTransaction())
				{
					session.SaveOrUpdate(author);
					transaction.Commit();

					System.Threading.Thread.Sleep(1000);  //Show IsBusy for demo
				}
			}
			catch (Exception ex)
			{
				if (author == null)
					_log.Fatal("Error in UpdateAuthor. author is null", ex);
				else
					_log.Fatal(string.Format("Error in UpdateAuthor. author.Id='{0}'", author.Id), ex);
				throw;
			}
		}

		#endregion author

		#region book

		[Query(IsComposable = true, HasSideEffects = true)]
		public IQueryable<BookDTO> GetBooks()
		{
			try
			{
				_log.Debug("GetBooks");

				System.Threading.Thread.Sleep(500); //Show IsBusy

				using (var session = _sessionFactory.OpenSession())
				using (var transaction = session.BeginTransaction())
				{
					// Use Fetch for eager-loading (Necessary in Webservice)
					// http://docs.jboss.org/hibernate/core/3.3/reference/en/html/performance.html
					var books = session.Query<Book>()
						.FetchMany(b => b.BookAuthors).ThenFetch(ba => ba.Author)
						.Fetch(b => b.Country)
						.ToList();
					transaction.Commit();

					// Copy to DTO
					var bookDtos = new List<BookDTO>();

					foreach (var book in books)
					{
						var bookDto = new BookDTO();
						bookDto.Id = book.Id;
						bookDto.Title = book.Title;
						bookDto.Price = book.Price;
						bookDto.IsOutOfStock = book.IsOutOfStock;
						bookDto.Release = book.Release;
						bookDto.CountryId = book.Country == null ? 0 : book.Country.Id;
						bookDto.Cover = book.Cover;

						bookDto.BookAuthorDtos = new List<BookAuthorDTO>();
						foreach (var bookAuthor in book.BookAuthors)
						{
							bookDto.BookAuthorDtos.Add
								(
									new BookAuthorDTO
										{
											Id = bookAuthor.Id,
											BookId = bookAuthor.Book.Id,
											AuthorId = bookAuthor.Author.Id,
											AuthorName = string.Format("{0} {1}", bookAuthor.Author.FirstName, bookAuthor.Author.LastName)
										}
								);
						}

						bookDtos.Add(bookDto);
					}

					return bookDtos.AsQueryable();
				}
			}
			catch (Exception ex)
			{
				_log.Fatal("Error in GetBooks.", ex);
				return null;
			}
		}

		public void InsertBook(BookDTO bookDto)
		{
			try
			{
				_log.DebugFormat("InsertBook. bookDto.Id='{0}'", bookDto.Id);

				var book = new Book();

				using (var session = _sessionFactory.OpenSession())
				using (var transaction = session.BeginTransaction())
				{
					var countryDb = session.Get<Country>(bookDto.CountryId);

					// Copy from DTO
					book.Id = bookDto.Id;
					book.Title = bookDto.Title;
					book.Price = bookDto.Price;
					book.IsOutOfStock = bookDto.IsOutOfStock;
					book.Release = bookDto.Release;
					book.Country = countryDb;
					book.Cover = bookDto.Cover;

					//book.BookAuthors = new List<BookAuthor>();
					foreach (var bookAuthorDto in bookDto.BookAuthorDtos)
					{
						var authorDb = session.Get<Author>(bookAuthorDto.AuthorId);
						var bookAuthorNew = new BookAuthor { Book = book, Author = authorDb };
						book.BookAuthors.Add(bookAuthorNew);
					}

					session.Save(book);
					transaction.Commit();

					// Update dto with ids from DB
					bookDto.Id = book.Id;

					foreach (var bookAuthor in book.BookAuthors)
					{
						var bookAuthorDto = bookDto.BookAuthorDtos.Where(x => x.AuthorId == bookAuthor.Author.Id).FirstOrDefault();
						if (bookAuthorDto != null)
						{
							bookAuthorDto.Id = bookAuthor.Id;
							bookAuthorDto.BookId = book.Id;
						}
					}
				}
			}
			catch (Exception ex)
			{
				_log.Fatal(
					bookDto == null
							? "Error in InsertBook. bookDto is null"
							: string.Format("Error in InsertBook. book.Id='{0}'", bookDto.Id), ex);
				throw;
			}
		}

		public void DeleteBook(BookDTO bookDto)
		{
			try
			{
				_log.DebugFormat("DeleteBook. bookDto.Id='{0}'", bookDto.Id);

				using (var session = _sessionFactory.OpenSession())
				using (var transaction = session.BeginTransaction())
				{
					var book = session.Get<Book>(bookDto.Id);
					if (book != null)
					{
						session.Delete(book); //Deletes all dependent BookAuthors because of .Cascade.AllDeleteOrphan()
						transaction.Commit();
					}
				}
			}
			catch (Exception ex)
			{
				_log.Fatal(
					bookDto == null
						? "Error in DeleteBook. bookDto is null"
						: string.Format("Error in DeleteBook. bookDto.Id='{0}'", bookDto.Id), ex);
				throw;
			}
		}

		// A much easier version of UpdateBook would work with Book, but .RIA returned null for the Books BookAuthor-Collection
		// See also BookAuthor.cs
		public void UpdateBook(BookDTO bookDto)
		{
			try
			{
				_log.DebugFormat("UpdateBook. bookDto.Id='{0}'", bookDto.Id);

				using (var session = _sessionFactory.OpenSession())
				using (var transaction = session.BeginTransaction())
				{
					// Get current book from DB
					var book = session.Query<Book>()
						.FetchMany(x => x.BookAuthors)
						.ThenFetch(u => u.Author)
						.Fetch(y => y.Country)
						.Where(v => v.Id == bookDto.Id)
						.FirstOrDefault();

					if (book == null)
						throw new ApplicationException("Buch nicht mehr in DB");

					var countryDb = session.Get<Country>(bookDto.CountryId);

					book.Title = bookDto.Title;
					book.Price = bookDto.Price;
					book.IsOutOfStock = bookDto.IsOutOfStock;
					book.Release = bookDto.Release;
					book.Country = countryDb;
					book.Cover = bookDto.Cover;

					// Add new bookAuthor from dto
					foreach (var bookAuthorDto in bookDto.BookAuthorDtos)
					{
						var bookAuthorDb = book.BookAuthors.Where(x => x.Author.Id == bookAuthorDto.AuthorId).FirstOrDefault();
						if (bookAuthorDb == null)
						{
							var authorDb = session.Get<Author>(bookAuthorDto.AuthorId);
							var bookAuthorNew = new BookAuthor { Book = book, Author = authorDb };
							book.BookAuthors.Add(bookAuthorNew);
						}
						else
							bookAuthorDto.Id = bookAuthorDb.Id;  //A bookAuthorDto was first removed in GUI and then again added -> its Id is 0
					}

					// Remove bookAuthor not contained in dto
					var bookAuthorsToRemove = new List<BookAuthor>();
					foreach (var bookAuthor in book.BookAuthors)
					{
						var bookAuthorDto = bookDto.BookAuthorDtos.Where(x => x.AuthorId == bookAuthor.Author.Id).FirstOrDefault();
						if (bookAuthorDto == null)
							bookAuthorsToRemove.Add(bookAuthor);
					}

					foreach (var bookAuthor in bookAuthorsToRemove)
						book.BookAuthors.Remove(bookAuthor);

					// Update DB
					session.SaveOrUpdate(book);
					foreach (var bookAuthor in bookAuthorsToRemove)
						session.Delete(bookAuthor);

					transaction.Commit();

					// Update dto with bookAuthor.Id 
					foreach (var bookAuthor in book.BookAuthors)
					{
						var bookAuthorDto = bookDto.BookAuthorDtos.Where(x => x.AuthorId == bookAuthor.Author.Id).FirstOrDefault();
						if (bookAuthorDto != null)
							bookAuthorDto.Id = bookAuthor.Id;
					}
				}
			}
			catch (Exception ex)
			{
				_log.Fatal(
					bookDto == null
						? "Error in UpdateBook. bookDto is null"
						: string.Format("Error in UpdateBook. bookDto.Id='{0}'", bookDto.Id), ex);

				throw;
			}
		}

		#endregion book
	}
}


